#include <GameEngine/Graphics/Camera/camera_manipulator_examine.h>
#include <GameEngine/Geometry/Core/angle.h>
#include <GameEngine/Geometry/Surfaces/plane3d.h>


#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

#ifndef DOXYGEN_IGNORE
#include "camera_manipulator_examine.moc"
#endif

//=================================================================================


//! Default constructor
CameraManipulatorExamine::CameraManipulatorExamine() : CameraManipulator() {}

//! Copy constructor
/*!
 @param rhs The camera manipulator to copy
 */
CameraManipulatorExamine::CameraManipulatorExamine(const CameraManipulatorExamine& rhs) : CameraManipulator(rhs) {}

//! Assignment operator
/*!
 @param rhs The camera manipulator to copy
 @return A reference to this camera manipulator
 */
CameraManipulatorExamine& CameraManipulatorExamine::operator=(const CameraManipulatorExamine& rhs) {
	CameraManipulator::operator=(rhs);
	return *this;
}

//! Destructor
CameraManipulatorExamine::~CameraManipulatorExamine() {}

//! Apply the given manipulation to the camera
/*!
 *  This is the specific implementation for an CameraManipulatorExamine.
 *
 *  The CameraManipulatorExamine allows to zoom, pan and rotate around an object
 *  in order to examine it. It is typically meant for an object editor (although
 *  it is not limited to this purpose).
 *
 @param button The mouse button that was pressed
 @param mouse_move The total amout of displacement of the mouse on the screen
 @return True if given manipulation could be applied to the camera, false otherwise
 */
bool CameraManipulatorExamine::manipulate(MouseButton button, const Vector2d<int>& mouse_move) {
	// Check whether mouse WAS moved
	if ( mouse_move.square_length() == 0 ) return false;

	// Scan all bindings
	bool res = false;
	for ( QList< QPair<MouseButton, CameraEffect> >::const_iterator it = bindings_.begin(); it != bindings_.end(); ++it ) {
		if ( it->first != button ) continue;

		// Camera should be zoomed
		if ( it->second == EffectZoom ) {
			// Get zoom movement
			float zoom = ( mouse_move.x() + mouse_move.y() ) * zoom_speed_;

			// Zoom camera
			camera_.zoom() += zoom;
			if ( camera_.zoom() > 0.95f ) camera_.zoom() = 0.95f;
			res = true;
			continue;
		}

		// Camera should be panned
		if ( it->second == EffectPan ) {
			// Get the normal orientations
			const Point3d<float>& eye = camera_.eye();
			const Point3d<float>& target = camera_.target();
			Vector3d<float> n = camera_.up().cross( Vector3d<float>(eye, target) ).normalize();
			Vector3d<float> up = camera_.up().normalize();

			// Get pan movement
			Vector3d<float> move = ( n * mouse_move.x() + up * mouse_move.y() ) * pan_speed_;

			// Pan camera
			camera_.eye() = Point3d<float>(eye.x()+move.x(),eye.y()+move.y(),eye.z()+move.z());
			camera_.target() = Point3d<float>(target.x()+move.x(),target.y()+move.y(),target.z()+move.z());
			res = true;
			continue;
		}

		// Camera should be rotated
		if ( it->second == EffectRotate ) {
			// Get the camera features
			const Point3d<double>& eye = camera_.eye();
			const Point3d<double>& target = camera_.target();

			// Get lateral rotate movement
			Point3d<double> new_eye;
			{{
				// Get the circle orientations
				Plane3d<double> plane(eye, camera_.up());
				Point3d<double> center = plane.project(target);
				Vector3d<double> ref = Vector3d<double>(center,eye).normalize();
				Vector3d<double> n = camera_.up().cross(ref).normalize();

				// Compute new position of the observer
				Angle<double> angle(-mouse_move.x() * rotation_speed_, Angles::Deg);
				double cos_a = cos(angle.rad()) - cos(0.0), sin_a = sin(angle.rad()) - sin(0.0);
				Vector3d<double> move = ref * cos_a + n * sin_a;
				new_eye = Point3d<double>( eye.x()+move.x(),eye.y()+move.y(),eye.z()+move.z() );

				// Correction of the distance (numeric inaccuracies)
				float coeff = center.get_distance(eye) / Vector3d<float>(center, new_eye).length();
				new_eye = Vector3d<double>(Point3d<double>(0,0,0),center) + Vector3d<double>(center, new_eye)*coeff;
			}}

			// Get vertical rotate movement
			{{
				// Get the circle orientations
				Vector3d<double> up = camera_.up(); up.normalize();
				Vector3d<double> tmp = up.cross( Vector3d<float>(target, new_eye) ).normalize();
				Vector3d<double> ref = up.cross( tmp ).normalize();

				// Compute new position of the observer
				Angle<double> angle(mouse_move.y() * rotation_speed_, Angles::Deg);
				double cos_a = cos(angle.rad()) - cos(0.0), sin_a = sin(angle.rad()) - sin(0.0);
				Vector3d<double> move = ref * cos_a + up * sin_a;
				new_eye = Point3d<double>( new_eye.x()+move.x(),new_eye.y()+move.y(),new_eye.z()+move.z() );

				// Correction of the distance (numeric inaccuracies)
				float coeff = target.get_distance(eye) / Vector3d<float>(target, new_eye).length();
				new_eye = Vector3d<double>(Point3d<double>(0,0,0),target) + Vector3d<double>(target, new_eye)*coeff;
			}}

			// Rotate camera
			camera_.eye() = new_eye;

			// Update the up vector
			{{
				Vector3d<double> up = camera_.up(); up.normalize();
				Vector3d<double> tmp = up.cross( Vector3d<float>(target, new_eye) ).normalize();
				camera_.up() = Vector3d<float>(target, new_eye).cross( tmp ).normalize();
			}}

			res = true;
			continue;
		}
	}
	if ( res ) emit manipulated();
	return res;
}


#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif
